use std::rc::Rc;

use cgmath::{vec2, vec4, Matrix4, SquareMatrix, Vector2, Vector4};
use tetris_core::tetris;
use wgpu::{include_wgsl, vertex_attr_array};

use super::{
    framework::{
        material::Material, mesh::MeshInstance, shader::Shader, uniform::Uniform, Attribute,
    },
    get_val_color, AsBytes, Vertex, QUAD_SIZE,
};

#[repr(C)]
#[derive(Clone, Copy)]
pub struct Vertex2D {
    vertex: [f32; 2],
}

pub fn v2d(v: Vector2<f32>) -> Vertex2D {
    Vertex2D { vertex: [v.x, v.y] }
}

impl Vertex for Vertex2D {
    const ATTRS: &'static [wgpu::VertexAttribute] = &vertex_attr_array![0 => Float32x2];
}

impl AsBytes for Vertex2D {}

pub struct Renderer {
    pub material: Material,
    mesh: MeshInstance<Vertex2D>,
}

impl Renderer {
    pub fn new(ctx: &super::Context, global_layout: &wgpu::BindGroupLayout) -> Self {
        let uniform = Uniform::<Appearence>::new_default(ctx, wgpu::ShaderStages::VERTEX_FRAGMENT);

        let pipeline_layout = ctx.create_pipeline_layout(&[&global_layout, uniform.layout()]);

        let shader =
            Shader::with_vertex_screen::<Vertex2D>(ctx, include_wgsl!("../../shaders/tetris.wgsl"));

        let pipeline = ctx.create_simple_render_pipeline(
            &pipeline_layout,
            &shader,
            wgpu::PrimitiveState {
                front_face: wgpu::FrontFace::Cw,
                cull_mode: Some(wgpu::Face::Back),
                ..Default::default()
            },
        );

        let mut material = Material::new(Rc::new(pipeline));
        material.put(1, Box::new(uniform));

        Self {
            material,
            mesh: MeshInstance::with_capacity(ctx, 16, 24),
        }
    }

    pub fn with_pipeline(ctx: &super::Context, tetris_pipeline: Rc<wgpu::RenderPipeline>) -> Self {
        let uniform = Uniform::<Appearence>::new_default(ctx, wgpu::ShaderStages::VERTEX_FRAGMENT);
        let mut material = Material::new(tetris_pipeline);
        material.put(1, Box::new(uniform));

        Self {
            material,
            mesh: MeshInstance::with_capacity(ctx, 16, 24),
        }
    }

    pub fn set_position(&mut self, position: Vector2<f32>) {
        self.material
            .attr_mut::<Uniform<Appearence>>(1)
            .unwrap()
            .data_mut()
            .model = Matrix4::from_translation(position.extend(0.0));
    }

    pub fn sync(&mut self, tetris: &tetris::Tetris) {
        self.material
            .attr_mut::<Uniform<Appearence>>(1)
            .unwrap()
            .data_mut()
            .model =
            Matrix4::from_translation(tetris.position().map(|i| i as f32 + 0.5).extend(0.0))
                * tetris.rotation().into_matrix();
    }

    pub fn reset(&mut self, tetris: &tetris::Tetris) {
        self.sync(tetris);

        self.reset_by_model(tetris.model());
    }

    pub fn reset_by_model(&mut self, model: &tetris::Model) {
        self.material
            .attr_mut::<Uniform<Appearence>>(1)
            .unwrap()
            .data_mut()
            .color = get_val_color(model.val);

        self.mesh.clear();

        for block in &model.blocks {
            let origin = vec2(block.x as f32, block.y as f32);

            let rt = origin + 0.5 * QUAD_SIZE;
            let lb = origin - 0.5 * QUAD_SIZE;
            let rb = lb + vec2(QUAD_SIZE.x, 0.0);
            let lt = lb + vec2(0.0, QUAD_SIZE.y);

            self.mesh.add_quad([v2d(lb), v2d(lt), v2d(rt), v2d(rb)]);
        }
    }

    pub fn prepare(&mut self, ctx: &super::Context) {
        self.material.prepare(ctx);
        self.mesh.prepare(ctx);
    }

    pub fn render<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>) {
        self.material.set(pass);
        self.mesh.render(pass);
    }
}

#[repr(C)]
pub struct Appearence {
    pub color: Vector4<f32>,
    pub model: Matrix4<f32>,
}

impl AsBytes for Appearence {}

impl Default for Appearence {
    fn default() -> Self {
        Self {
            color: vec4(0.0, 0.0, 0.0, 1.0),
            model: Matrix4::identity(),
        }
    }
}

// pub struct Material {
//     pipeline: Rc<wgpu::RenderPipeline>,
//     uniform: Uniform<Appearence>,
// }

// impl Material {
//     pub fn new(ctx: &super::Context, global_layout: &wgpu::BindGroupLayout) -> Self {
//         let uniform = Uniform::new_default(ctx, wgpu::ShaderStages::VERTEX_FRAGMENT);

//         let pipeline_layout = ctx.create_pipeline_layout(&[&global_layout, uniform.layout()]);

//         let shader = Shader::with_vertex_screen::<Vertex2D>(ctx, include_wgsl!("../../shaders/tetris.wgsl"));

//         let pipeline = ctx.create_simple_render_pipeline(
//             &pipeline_layout,
//             &shader,
//             wgpu::PrimitiveState {
//                 front_face: wgpu::FrontFace::Cw,
//                 cull_mode: Some(wgpu::Face::Back),
//                 ..Default::default()
//             },
//         );

//         Self {
//             pipeline: Rc::new(pipeline),
//             uniform,
//         }
//     }

//     pub fn with_pipeline(ctx: &super::Context, pipeline: Rc<wgpu::RenderPipeline>) -> Self {
//         let uniform = Uniform::new(ctx, wgpu::ShaderStages::VERTEX_FRAGMENT, Default::default());
//         Self { pipeline, uniform }
//     }

//     pub fn set_color(&mut self, color: Vector4<f32>) {
//         self.uniform.data_mut().color = color;
//     }

//     pub fn set_model_matrix(&mut self, model: Matrix4<f32>) {
//         self.uniform.data_mut().model = model;
//     }

//     pub fn pipeline(&self) -> Rc<wgpu::RenderPipeline> {
//         self.pipeline.clone()
//     }

//     pub fn prepare(&mut self, ctx: &super::Context) {
//         self.uniform.prepare(ctx)
//     }

//     pub fn set<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>) {
//         pass.set_pipeline(&self.pipeline);
//         pass.set_bind_group(1, self.uniform.bind_group(), &[])
//     }
// }
